/*******************************************************************************
 * Copyright (c) 2010-2011 Robert Fraser                                       *
 *                                                                             *
 * Permission is hereby granted, free of charge, to any person obtaining a     *
 * copy of this software and associated documentation files (the "Software"),  *
 * to deal in the Software without restriction, including without limitation   *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,    *
 * and/or sell copies of the Software, and to permit persons to whom the       *
 * Software is furnished to do so, subject to the following conditions:        *
 *                                                                             *
 *   - The above copyright notice and this permission notice shall be included *
 *     in all copies or substantial portions of the Software.                  *
 *                                                                             *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER      *
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     *
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         *
 * DEALINGS IN THE SOFTWARE.                                                   *
 *******************************************************************************/

using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using zeal.util;

namespace zeal.ui.util
{
    internal sealed partial class AsyncProgressMonitor : DependencyObject, IProgressMonitor, IDisposable
    {
        public static readonly TimeSpan DEFAULT_UPDATE_FREQUENCY = TimeSpan.FromSeconds(0.5);

        private readonly IProgressMonitor _pm;
        private readonly bool _cancellable;
        private DispatcherTimer _timer;
        private int _isTimerStopped;

        public AsyncProgressMonitor(IProgressMonitor pm) : this(pm, DEFAULT_UPDATE_FREQUENCY) { }
        public AsyncProgressMonitor(IProgressMonitor pm, TimeSpan updateFrequency)
        {
            if (pm == null) throw new ArgumentNullException("pm");
            _pm = pm;

            _cancellable = pm.cancellable;
            workComplete = pm.workComplete;
            totalWork = pm.totalWork;
            task = pm.task ?? string.Empty;
            cancelled = pm.cancelled;

            Bindings.set(this, startedProperty, this, totalWorkProperty, tw => tw >= 0);
            Bindings.set(this, completedProperty, Bindings.multi(Bindings.get(this, totalWorkProperty), Bindings.get(this, workCompleteProperty), (tw, wc) => tw >= 0 && wc >= tw));
            Bindings.set(this, workRemainingProperty, Bindings.multi(Bindings.get(this, totalWorkProperty), Bindings.get(this, workCompleteProperty), (tw, wc) => tw >= 0 ? Math.Max(0, tw - wc) : 0));
            Bindings.set(this, percentCompleteProperty, Bindings.multi(Bindings.get(this, totalWorkProperty), Bindings.get(this, workCompleteProperty), (tw, wc) => tw >= 0 ? Math.Min(1d, (double) wc / tw) : 0d));

            if(!cancelled && !completed)
            {
                _timer = new DispatcherTimer(DispatcherPriority.Normal, Dispatcher);
                _timer.Tick += onTick;
                _timer.Interval = updateFrequency;
                _timer.Start();
            }
            else
            {
                Thread.VolatileWrite(ref _isTimerStopped, 1);
            }
        }

        private void onTick(object sender, EventArgs eventArgs) { update(); }
        private void update()
        {
            totalWork = _pm.totalWork;
            workComplete = _pm.workComplete;
            task = _pm.task;
            cancelled = _pm.cancelled;
            if(completed || cancelled) killTimer();
        }

        public bool cancellable { get { return _cancellable; }}
        public void cancel()
        {
            VerifyAccess();
            if(cancelled) return;
            if(!cancellable) throw new InvalidOperationException("The progress viewer is not cancellable");
            _pm.cancel();
            update();
        }
        
        ~AsyncProgressMonitor() { killTimer(); }
        public void Dispose() { killTimer(); }
        private void killTimer()
        {
            if(Interlocked.CompareExchange(ref _isTimerStopped, 1, 0) == 0)
            {
                _timer.Stop();
                _timer.Tick -= onTick;
                _timer = null;
            }
        }
    }
}